package com.hup.utils.swing.customView;

import com.hup.utils.commons.io.FileUtil;
import com.hup.utils.swing.ComponentUtil;
import com.sun.java.swing.plaf.windows.WindowsFileChooserUI;
import sun.awt.shell.ShellFolder;
import sun.swing.FilePane;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JList;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.basic.BasicFileChooserUI;
import javax.swing.table.TableColumn;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Field;
import java.util.Objects;

/**
 * 实现控件双击时,弹窗选择文件;和中途的记住路径功能;
 * 允许看到所有文件的情况下(方便用户认知当前目录),只能选择固定类型的文件
 *
 * @author hugan
 * @date 2019/8/31
 */
public interface BaseFileChooser {

    default void initFileChooserListener() {
        ((JComponent) this).addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                JComponent c = (JComponent) e.getSource();
                if (!c.isEnabled()) {
                    return;
                }
                if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
                    showFileChooser(null);
                }
            }
        });
    }

    default void showFileChooser(JFileChooser fc) {
        if (fc == null) {
            fc = new FixedJFileChooser(getExtFilterText());
        }
        File path = new File(String.valueOf(this.getRecentPath()));
        //Logger.getLogger(this.getClass().getName()).info("path=" + path.toString());
        if (!path.exists()) {
            File parentFile = path.getParentFile();
            if (parentFile == null || !parentFile.exists()) {
                path = new File("./");//默认是当前程序目录
            } else {
                //文件不存在但parent存在
                path = parentFile;
            }
        }
        fc.setCurrentDirectory(path);

        //弹窗并等待用户选择
        JComponent c = (JComponent) this;
        int res = fc.showDialog(c, getChooserBtnTxt());
        File currentDirectory = fc.getCurrentDirectory();
        //log.debug("currentDirectory={}", currentDirectory);

        /*
        此处小bug:
            .选择了[盘符]时, currentDirectory.getAbsolutePath()=="程序路径\ShellFolder: 0x11"
            .导致下次打开弹窗,在上面的代码 getRecentPath时目录不存在
            .目前不处理
         */
        this.setRecentPath(currentDirectory.getAbsolutePath());
        if (res != JFileChooser.APPROVE_OPTION) {
            return;
        }

        File file = fc.getSelectedFile();
        if (file == null) return;

        try {
            //是快捷方式:定位到源文件
            ShellFolder sf = FileUtil.getShellFolder(file);
            if (sf.isLink()) file = FileUtil.getLinkLocation(sf);
        } catch (FileNotFoundException e) {
            showFileChooser(fc);//一般是不会发生的
            return;
        }

        //如果不是[符合格式]就重新打开窗口
        if (getFileSelectionMode() == JFileChooser.FILES_ONLY) {
            if (file.isDirectory() || !this.isFileMatch(file)) {
                showFileChooser(fc);
                return;
            }
        } else if(getFileSelectionMode() == JFileChooser.DIRECTORIES_ONLY) {
            if (!file.isDirectory()) {
                showFileChooser(fc);
                return;
            }
        }

        String chosePath = file.getAbsolutePath();
        this.setText(chosePath);

        ChoseListener choseListener = this.getChoseListener();
        if (choseListener != null) {
            choseListener.afterChose(chosePath);
        }
    }

    /**
     * FILES_ONLY, DIRECTORIES_ONLY, FILES_AND_DIRECTORIES
     */
    default int getFileSelectionMode() {
        return JFileChooser.FILES_ONLY;
    }

    String getRecentPath();

    void setRecentPath(String recentPath);

    /**
     * 获取文件类型的文本说明,如"[exe,jar]"
     *
     * @return null-没有文件类型要求
     */
    String getExtFilterText();

    boolean isFileMatch(File file);

    void setText(String filePath);

    ChoseListener getChoseListener();

    void setChoseListener(ChoseListener choseListener);

    default String getChooserBtnTxt() {
        return ((JComponent) this).getToolTipText();
    }

    interface ChoseListener {
        void afterChose(String chosePath);
    }

    /**
     * 用非正常方式修正的[文件选择器],修正内容:
     * .打开时显示详细信息视图
     * .文件类型ComboBox的文本
     * .[文件名,大小]列的列宽
     */
    class FixedJFileChooser extends JFileChooser {
        /**
         * 一个中文的宽度;用于修复[文件名列]的宽度
         */
        private int CHAR_WIDTH;
        private FilePane filePane;
        private String lastDir;

        public FixedJFileChooser(String filterExtText) {
            setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
            //setFileFilter(new FileNameExtensionFilter("*.zip", "zip"));//这样会看不到其他文件

            try {
                doInitFix(filterExtText);
            } catch (Exception e) {
                //do nothing
            }
        }

        @SuppressWarnings("unchecked")
        private void doInitFix(String filterExtText) throws Exception {
            filePane = ComponentUtil.findChild(this, FilePane.class);
            if (filePane != null) {
                //修复:打开时显示详细信息视图
                filePane.setViewType(FilePane.VIEWTYPE_DETAILS);
                CHAR_WIDTH = getFontMetrics(filePane.getFont()).stringWidth("字");
            }

            //修改文件类型ComboBox的文本
            if (filterExtText != null) {
                //通过反射,ComboBox只有一项
                WindowsFileChooserUI ui = (WindowsFileChooserUI) this.getUI();
                Field fFilterComboBox = ui.getClass().getDeclaredField("filterComboBox");
                fFilterComboBox.setAccessible(true);
                JComboBox<FileFilter> filterComboBox = (JComboBox<FileFilter>) fFilterComboBox.get(ui);
                filterComboBox.setRenderer(new ExtFilterCbRenderer(filterExtText));
            }
        }

        @Override
        public void setCurrentDirectory(File dir) {
            super.setCurrentDirectory(dir);
            if (dir == null) return;
            String absolutePath = dir.getAbsolutePath();
            if (Objects.equals(absolutePath, lastDir)) return;
            lastDir = absolutePath;
            //ShareLogger.info("dir={}", dir);
            fixColumnWidth(dir);
        }

        private void fixColumnWidth(File dir) {
            if (filePane == null || filePane.getViewType() != FilePane.VIEWTYPE_DETAILS) return;//用户修改了显示方式

            File[] children;
            JTable table;
            if ((children = dir.listFiles()) != null && children.length != 0
                    && (table = ComponentUtil.findChild(filePane, JTable.class)) != null) {
                //这个column是每次都是新对象来的(sun.swing.FilePane.updateDetailsColumnModel),所以每次都要找
                TableColumn nameColumn = getColumn(table, "FileChooser.fileNameHeaderText");
                if (nameColumn != null) {
                    String maxName = getFileDisplayName(children[0]);//最长的名字,但占用宽度不一定是最宽的,凑合用
                    for (File child : children) {
                        String name = getFileDisplayName(child);
                        //ShareLogger.info("name={}", name);
                        if (name.length() > maxName.length()) maxName = name;
                    }
                    int width = table.getFontMetrics(table.getFont()).stringWidth(maxName) + CHAR_WIDTH * 2;//2个字:图标等
                    nameColumn.setPreferredWidth(width);
                    //ShareLogger.info("width={}", width);
                }
                TableColumn sizeColumn = getColumn(table, "FileChooser.fileSizeHeaderText");
                if (sizeColumn != null) sizeColumn.setPreferredWidth(CHAR_WIDTH * 6);
            }
        }

        /**
         * @param key key参考自sun.awt.shell.ShellFolder.getFolderColumns(java.io.File)
         */
        private TableColumn getColumn(JTable table, String key) {
            try {
                String temp = UIManager.getString(key, this.getLocale());
                if (temp != null) key = temp;
                return table.getColumn(key);
            } catch (Exception e) {
                return null;
            }
        }

        private String getFileDisplayName(File file) {
            if (file instanceof ShellFolder) {
                //处理[我的电脑]中的分区名
                return ((ShellFolder) file).getDisplayName();
            } else {
                return file.getName();
            }
        }

        /**
         * 设置文件名控件的文本内容
         *
         * @apiNote 仅影响该控件, 不会修改其他数据和界面
         */
        public void setFileName(String fileName) {
            ((BasicFileChooserUI) getUI()).setFileName(fileName);
        }

        /**
         * 文件类型下拉框的渲染器;本类创建的选择器.下拉框永远只有一项
         */
        private static class ExtFilterCbRenderer extends DefaultListCellRenderer {
            private final String extFilterText;

            /**
             * @param extFilterText notEmptyString
             */
            public ExtFilterCbRenderer(String extFilterText) {
                this.extFilterText = extFilterText;
            }

            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index,
                    boolean isSelected, boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                setText(extFilterText);
                return this;
            }
        }
    }

}
